/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.1 Module
 * -------------------------------------------------
 *
 * Copyright 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Negative Sample Variables Tests
 *//*--------------------------------------------------------------------*/

#include "es31fNegativeSampleVariablesTests.hpp"
#include "gluShaderProgram.hpp"

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace NegativeTestShared
{
namespace
{

enum ExpectResult
{
	EXPECT_RESULT_PASS = 0,
	EXPECT_RESULT_FAIL,
	EXPECT_RESULT_LAST
};

void verifyShader (NegativeTestContext& ctx, glu::ShaderType shaderType, std::string shaderSource, ExpectResult expect)
{
	DE_ASSERT(expect >= EXPECT_RESULT_PASS && expect < EXPECT_RESULT_LAST);

	tcu::TestLog&		log			= ctx.getLog();
	bool				testFailed	= false;
	const char* const	source		= shaderSource.c_str();
	const int			length		= (int) shaderSource.size();
	glu::Shader			shader		(ctx.getRenderContext(), shaderType);
	std::string			message;

	shader.setSources(1, &source, &length);
	shader.compile();

	log << shader;

	if (expect == EXPECT_RESULT_PASS)
	{
		testFailed = !shader.getCompileStatus();
		message = "Shader did not compile.";
	}
	else
	{
		testFailed = shader.getCompileStatus();
		message = "Shader was not expected to compile.";
	}

	if (testFailed)
	{
		log << tcu::TestLog::Message << message << tcu::TestLog::EndMessage;
		ctx.fail(message);
	}
}

std::string getVersionAndExtension (NegativeTestContext& ctx)
{
	const bool				isES32	= contextSupports(ctx.getRenderContext().getType(), glu::ApiType::es(3, 2));
	const glu::GLSLVersion	version	= isES32 ? glu::GLSL_VERSION_320_ES : glu::GLSL_VERSION_310_ES;

	std::string versionAndExtension = glu::getGLSLVersionDeclaration(version);
	versionAndExtension += " \n";

	if (!isES32)
		versionAndExtension += "#extension GL_OES_sample_variables : require \n";

	return versionAndExtension;
}

void checkSupported (NegativeTestContext& ctx)
{
	const bool isES32orGL45 = contextSupports(ctx.getRenderContext().getType(), glu::ApiType::es(3, 2)) ||
							  contextSupports(ctx.getRenderContext().getType(), glu::ApiType::core(4, 5));

	if (!isES32orGL45 && !ctx.isExtensionSupported("GL_OES_sample_variables"))
		TCU_THROW(NotSupportedError, "GL_OES_sample_variables is not supported.");
}

void write_to_read_only_types (NegativeTestContext& ctx)
{
	checkSupported(ctx);

	std::ostringstream	shader;

	struct testConfig
	{
		std::string builtInType;
		std::string varyingCheck;
	} testConfigs[] =
	{
		{"gl_SampleID",			"	lowp int writeValue = 1; \n	gl_SampleID = writeValue; \n"},
		{"gl_SamplePosition",	"	mediump vec2 writeValue = vec2(1.0f, 1.0f); \n	gl_SamplePosition = writeValue; \n"},
		{"gl_SampleMaskIn",		"	lowp int writeValue = 1; \n	gl_SampleMaskIn[0] = writeValue; \n"}
	};

	for (int idx = 0; idx < DE_LENGTH_OF_ARRAY(testConfigs); idx++)
	{
		shader.str("");
		shader
			<< getVersionAndExtension(ctx)
			<< "layout (location = 0) out mediump vec4 fs_color; \n"
			<< "void main() \n"
			<< "{ \n"
			<<		testConfigs[idx].varyingCheck
			<< "	fs_color = vec4(1.0f, 0.0f, 0.0f, 1.0f); \n"
			<< "} \n";

		ctx.beginSection("OES_sample_variables: trying to write to built-in read-only variable" + testConfigs[idx].builtInType);
		verifyShader(ctx, glu::SHADERTYPE_FRAGMENT, shader.str(), EXPECT_RESULT_FAIL);
		ctx.endSection();
	}
}

void access_built_in_types_inside_other_shaders (NegativeTestContext& ctx)
{
	checkSupported(ctx);

	if (glu::isContextTypeES(ctx.getRenderContext().getType()))
	{
		if ((!ctx.isExtensionSupported("GL_EXT_tessellation_shader") && !ctx.isExtensionSupported("GL_OES_tessellation_shader")) ||
			(!ctx.isExtensionSupported("GL_EXT_geometry_shader") && !ctx.isExtensionSupported("GL_OES_geometry_shader")))
			TCU_THROW(NotSupportedError, "tessellation and geometry shader extensions not supported");
	}

	std::ostringstream	shader;

	struct testConfig
	{
		std::string builtInType;
		std::string varyingCheck;
	} testConfigs[] =
	{
		{"gl_SampleID",			"	lowp int writeValue = 1; \n	gl_SampleID = writeValue; \n"},
		{"gl_SamplePosition",	"	mediump vec2 writeValue = vec2(1.0f, 1.0f); \n	gl_SamplePosition = writeValue; \n"},
		{"gl_SampleMaskIn",		"	lowp int writeValue = 1; \n	gl_SampleMaskIn[0] = writeValue; \n"},
		{"gl_SampleMask",		"	highp int readValue = gl_SampleMask[0]; \n"},
	};

	for (int idx = 0; idx < DE_LENGTH_OF_ARRAY(testConfigs); idx++)
	{
		shader.str("");
		shader
			<< getVersionAndExtension(ctx)
			<< "void main () \n"
			<< "{ \n"
			<<		testConfigs[idx].varyingCheck
			<< "	gl_Position = vec4(1.0f, 0.0f, 0.0f , 1.0f); \n"
			<< "} \n";

		ctx.beginSection("OES_sample_variables: trying to use fragment shader built-in sampler variable " + testConfigs[idx].builtInType + " inside vertex shader");
		verifyShader(ctx, glu::SHADERTYPE_VERTEX, shader.str(), EXPECT_RESULT_FAIL);
		ctx.endSection();
	}

	for (int idx = 0; idx < DE_LENGTH_OF_ARRAY(testConfigs); idx++)
	{
		shader.str("");
		shader
			<< getVersionAndExtension(ctx)
			<< "layout (vertices = 3) out; \n"
			<< "void main () \n"
			<< "{ \n"
			<<		testConfigs[idx].varyingCheck
			<< "} \n";

		ctx.beginSection("OES_sample_variables: trying to use fragment shader built-in sampler variable " + testConfigs[idx].builtInType + " inside tessellation control shader");
		verifyShader(ctx, glu::SHADERTYPE_TESSELLATION_CONTROL, shader.str(), EXPECT_RESULT_FAIL);
		ctx.endSection();
	}

	for (int idx = 0; idx < DE_LENGTH_OF_ARRAY(testConfigs); idx++)
	{
		shader.str("");
		shader
			<< getVersionAndExtension(ctx)
			<< "layout (triangles, equal_spacing, ccw) in; \n"
			<< "void main () \n"
			<< "{ \n"
			<<		testConfigs[idx].varyingCheck
			<< "} \n";

		ctx.beginSection("OES_sample_variables: trying to use fragment shader built-in sampler variable " + testConfigs[idx].builtInType + " inside tessellation evaluation shader");
		verifyShader(ctx, glu::SHADERTYPE_TESSELLATION_EVALUATION, shader.str(), EXPECT_RESULT_FAIL);
		ctx.endSection();
	}

	for (int idx = 0; idx < DE_LENGTH_OF_ARRAY(testConfigs); idx++)
	{
		shader.str("");
		shader
			<< getVersionAndExtension(ctx)
			<< "layout (triangles) in; \n"
			<< "layout (triangle_strip, max_vertices = 32) out; \n"
			<< "void main () \n"
			<< "{ \n"
			<<		testConfigs[idx].varyingCheck
			<< "} \n";

		ctx.beginSection("OES_sample_variables: trying to use fragment shader built-in sampler variable " + testConfigs[idx].builtInType + " inside geometry shader");
		verifyShader(ctx, glu::SHADERTYPE_GEOMETRY, shader.str(), EXPECT_RESULT_FAIL);
		ctx.endSection();
	}
}

void index_outside_sample_mask_range (NegativeTestContext& ctx)
{
	// Skip this test for GL4.5 - shader will compile
	if (!glu::isContextTypeES(ctx.getRenderContext().getType()))
		return;

	checkSupported(ctx);

	std::ostringstream	shader;
	const int			MAX_TYPES	= 2;
	const int			MAX_INDEXES = 2;

	struct testConfig
	{
		std::string builtInType[MAX_TYPES];
		std::string invalidIndex[MAX_INDEXES];
	} testConfigs =
	{
		{
			"gl_SampleMask",
			"gl_SampleMaskIn"
		},
		{
			"	const highp int invalidIndex = (gl_MaxSamples + 31) / 32; \n",
			"	const highp int invalidIndex = -1; \n"
		}
	};

	for (int typeIdx = 0; typeIdx < MAX_TYPES; typeIdx++)
	{
		for (int invalidIdx = 0; invalidIdx < MAX_INDEXES; invalidIdx++)
		{
			shader.str("");
			shader
				<< getVersionAndExtension(ctx)
				<< "layout (location = 0) out mediump vec4 fs_color; \n"
				<< "void main() \n"
				<< "{ \n"
				<<		testConfigs.invalidIndex[invalidIdx]
				<< "	highp int invalidValue = " << testConfigs.builtInType[typeIdx] << "[invalidIndex]; \n"
				<< "	fs_color = vec4(1.0f, 0.0f, 0.0f, 1.0f); \n"
				<< "} \n";

			ctx.beginSection("OES_sample_variables: using constant integral expression outside of " + testConfigs.builtInType[typeIdx] + " bounds");
			verifyShader(ctx, glu::SHADERTYPE_FRAGMENT, shader.str(), EXPECT_RESULT_FAIL);
			ctx.endSection();
		}
	}
}

void access_built_in_types_without_extension (NegativeTestContext& ctx)
{
	checkSupported(ctx);

	std::ostringstream	shader;

	struct testConfig
	{
		std::string builtInType;
		std::string varyingCheck;
	} testConfigs[] =
	{
		{"gl_SampleID",			"	lowp int writeValue = 1; \n	gl_SampleID = writeValue; \n"},
		{"gl_SamplePosition",	"	mediump vec2 writeValue = vec2(1.0f, 1.0f); \n	gl_SamplePosition = writeValue; \n"},
		{"gl_SampleMaskIn",		"	lowp int writeValue = 1; \n	gl_SampleMaskIn[0] = writeValue; \n"},
		{"gl_SampleMask",		"	highp int readValue = gl_SampleMask[0]; \n"},
	};

	for (int idx = 0; idx < DE_LENGTH_OF_ARRAY(testConfigs); idx++)
	{
		shader.str("");
		shader
			<< "#version 310 es \n"
			<< "layout (location = 0) out mediump vec4 fs_color; \n"
			<< "void main() \n"
			<< "{ \n"
			<<		testConfigs[idx].varyingCheck
			<< "	fs_color = vec4(1.0f, 0.0f, 0.0f, 1.0f); \n"
			<< "} \n";

		ctx.beginSection("OES_sample_variables: accessing built-in type " + testConfigs[idx].builtInType + " in shader version 310 ES without required extension");
		verifyShader(ctx, glu::SHADERTYPE_FRAGMENT, shader.str(), EXPECT_RESULT_FAIL);
		ctx.endSection();
	}
}

void redeclare_built_in_types (NegativeTestContext& ctx)
{
	// skip this test for core GL
	if (glu::isContextTypeGLCore(ctx.getRenderContext().getType()))
		return;

	checkSupported(ctx);

	std::ostringstream	shader;
	std::ostringstream	testName;

	const char* const testConfigs[] =
	{
		"gl_SampleID",
		"gl_SamplePosition",
		"gl_SampleMaskIn",
		"gl_SampleMask",
	};

	for (int idx = 0; idx < DE_LENGTH_OF_ARRAY(testConfigs); idx++)
	{
		shader.str("");
		shader
			<< getVersionAndExtension(ctx)
			<< "layout (location = 0) out mediump vec4 fs_color; \n"
			<< "uniform lowp int " << testConfigs[idx] << "; \n"
			<< "void main() \n"
			<< "{ \n"
			<< "	if (" << testConfigs[idx] << " == 0) \n"
			<< "		fs_color = vec4(1.0f, 0.0f, 0.0f, 1.0f); \n"
			<< "	else \n"
			<< "		fs_color = vec4(0.0f, 1.0f, 0.0f, 1.0f); \n"
			<< "} \n";

		testName.str("");
		testName << "OES_sample_variables: redeclare built-in type " << testConfigs[idx];
		ctx.beginSection(testName.str());
		verifyShader(ctx, glu::SHADERTYPE_FRAGMENT, shader.str(), EXPECT_RESULT_FAIL);
		ctx.endSection();
	}
}

} // anonymous

std::vector<FunctionContainer> getNegativeSampleVariablesTestFunctions (void)
{
	const FunctionContainer funcs[] =
	{
		{write_to_read_only_types,						"write_to_read_only_types",						"tests trying writing to read-only built-in sample variables"},
		{access_built_in_types_inside_other_shaders,	"access_built_in_types_inside_other_shaders",	"Tests try to access fragment shader sample variables in other shaders"},
		{index_outside_sample_mask_range,				"index_outside_sample_mask_range",				"tests try to index into built-in sample array types out of bounds"},
		{access_built_in_types_without_extension,		"access_built_in_types_without_extension",		"tests try to access built-in sample types without the correct extension using version 310 es"},
		{redeclare_built_in_types,						"redeclare_built_in_types",						"Tests try to redeclare built-in sample types"},
	};

	return std::vector<FunctionContainer>(DE_ARRAY_BEGIN(funcs), DE_ARRAY_END(funcs));
}

} // NegativeTestShared
} // Functional
} // gles31
} // deqp
